/*CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
 C      scheduler.c
 C      Application Name: tcss422-project2
 C      Programmers Names: Evan Phillips, Lucas Cowell, Tony Butterfield
 C      Date created:   May 24, 2011
 C      Date Completed: 
 C      Brief Description: 
 C
 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC*/
#include <stdlib.h>
#include <stdio.h>
#include "definitions.h"
#include "scheduler.h"
#include "cpu_sim.h"
#include "process.h"
#include "process_queue.h"
#include "io_device.h"
#include "semaphore.h"

/*=================================================================================================
 =  Comment block for function: sched_init()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      Scheduler sched_init()
 =      Return value ranges: n/a
 =      Parameter value ranges: n/a
 =      Error conditions: N/a
 =  Initializes scheduler.
 =
 ==================================================================================================*/
Scheduler sched_init(CPU cpu)
{
    Scheduler s = (Scheduler) malloc(sizeof(SchedulerStr));
    s->ready_queue = p_queue_init();

    s->cpu = cpu;
    return s;
}

/*=================================================================================================
 =  Comment block for function: sched_setReady()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_setReady(Scheduler, Process)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process
 =      Error conditions: NULL_POINTER_ERROR
 =  Enqueues process into the scheduler's ready queue and set its state to P_READY.
 =
 ==================================================================================================*/
int sched_setReady(Scheduler s, Process p)
{
    int error = 0;
    if (s == NULL || p == NULL) return NULL_POINTER_ERROR;

    printf("(%s[%d] to Ready)",p->pcb->name, p_getPID(p, &error));

    p_setState(p, P_READY);
    error |= p_queue_enqueue(s->ready_queue, p); // enqueue returns an error code, so catch it.

    return error;
}


/*=================================================================================================
 =  Comment block for function: sched_getNextReady()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_getNextReady(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler
 =      Error conditions: NULL_POINTER_ERROR
 =  Dequeues process from scheduler's ready queue and sets it as cpu's running process.
 =
 ==================================================================================================*/
int sched_getNextReady(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;                   // just exit if null.

    Process p = p_queue_dequeue(s->ready_queue, &error);        // get next process

    if (p_getPID(p, &error) == 0)                               // if idle
    {
        if (p_queue_isEmpty(s->ready_queue, &error))            // and if no other processes in queue
        {
            printf("\n\n(CPU is IDLE)");                        // print warning
        }
        else                                                    // but if there are other processes
        {
            p_queue_enqueue(s->ready_queue, p);                 // reqeueue the idle
            p = p_queue_dequeue(s->ready_queue, &error);        // and get the next process
        }
    }

    if (p_getPID(p, &error) != 0)
    {
        printf("\n\n(%s[%d] to Running)", p->pcb->name, p_getPID(p, &error));
    }
    p_setState(p, P_RUNNING);
    s->cpu->running_process = p;
    s->cpu->program_counter = p_getNextStep(p, &error);
    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_setBlocked()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_setBlocked(Scheduler, Process, ProcessQueue)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process
 =      Error conditions: NULL_POINTER_ERROR
 =  Enqueues process into the indicated blocked queue and sets its state to P_BLOCKED.
 =
 ==================================================================================================*/
int sched_setBlocked(Scheduler s, ProcessQueue q)
{
    int error = 0;
    if (s == NULL || q == NULL) return NULL_POINTER_ERROR;  // just exit if null.

    Process p = s->cpu->running_process;
    printf("(%s[%d] to Blocked)", p->pcb->name, p_getPID(p, &error));

    error |= p_setState(p, P_BLOCKED);              // set state
    error |= p_queue_enqueue(q, p);                 // put process in block queue.
    error |= sched_getNextReady(s);                 // get next ready process

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_getBlocked()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_getBlocked(Scheduler, ProcessQueue)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Dequeues process from block queue and enqueues it in the Scheduler's ready queue, marking it
 =  as P_READY.
 =
 ==================================================================================================*/
int sched_getBlocked(Scheduler s, ProcessQueue q)
{
    int error = 0;
    if (s == NULL || q == NULL) return NULL_POINTER_ERROR;

//    Process p = p_queue_peek(q, &error);
//    int pid = p_getPID(p, &error);
//    char* name = p->pcb->name;
//    printf("(%d)%s put in Ready Queue", pid, name);

    error |= sched_setReady(s, p_queue_dequeue(q, &error));

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_processInterrupts()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_processInterrupts(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler
 =      Error conditions: NULL_POINTER_ERROR
 =  Tests each bit in the IC, and uses a switch to launch the appropriate handler, in priority order.
 =  Priority order:  SYS_TIMER_INTERRUPT
 =                   SYS_EXCEPTION_DIV_BY_0
 =                   IO_DEV_KBD_INTERRUPT
 =                   IO_DEV_DSK_INTERRUPT
 =                   IO_DEV_VID_INTERRUPT
 =                   IO_KBD_REQUEST
 =                   IO_DSK_REQUEST
 =                   IO_VID_REQUEST
 =                   SYS_SEM_UP_REQUEST
 =                   SYS_SEM_DWN_REQUEST... and any others we need.  if this order isn't right
 =                                          we can switch the order of the tests...
 =
 ==================================================================================================*/
int sched_processInterrupts(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    while (s->cpu->interrupt_controller != 0)   // loop until we clear all interrupts in IC
    {
        // NOTE:  for the purposes of this sim, the interrupts that cause a context change have to
        // be processed after the system requests that need to move the calling process to the proper
        // blocking queue...


        // keyboard IO system request
        if (s->cpu->interrupt_controller & IO_KBD_REQUEST)
        {
            error |= sched_kbdRequestHandler(s);
            s->cpu->interrupt_controller &= !IO_KBD_REQUEST;
        }

        // disk IO system request
        if (s->cpu->interrupt_controller & IO_DSK_REQUEST)
        {
            error |= sched_dskRequestHandler(s);
            s->cpu->interrupt_controller &= !IO_DSK_REQUEST;
        }

        // video IO system request
        if (s->cpu->interrupt_controller & IO_VID_REQUEST)
        {
            error |= sched_vidRequestHandler(s);
            s->cpu->interrupt_controller &= !IO_VID_REQUEST;
        }

        // sem down request
        if (s->cpu->interrupt_controller & SYS_SEM_DWN_REQUEST)
        {
            error |= sched_semDwnRequestHandler(s);
            s->cpu->interrupt_controller &= !SYS_SEM_DWN_REQUEST;
        }

        // Sem Up request
        if (s->cpu->interrupt_controller & SYS_SEM_UP_REQUEST)
        {
            error |= sched_semUpRequestHandler(s);
            s->cpu->interrupt_controller &= !SYS_SEM_UP_REQUEST;
        }

        // System timer
        if (s->cpu->interrupt_controller & SYS_TIMER_INTERRUPT)
        {
            error |= sched_timerInterruptHandler(s);
            s->cpu->interrupt_controller &= !SYS_TIMER_INTERRUPT;
        }

        // hardware exception
        if (s->cpu->interrupt_controller & SYS_EXCEPTION_DIV_BY_0)
        {
            error |= sched_hardwareExceptionHandler(s);
            s->cpu->interrupt_controller &= !SYS_EXCEPTION_DIV_BY_0;
        }

        // keyboard IO interrupt
        if (s->cpu->interrupt_controller & IO_DEV_KBD_INTERRUPT)
        {
            error |= sched_kbdInterruptHandler(s);
            s->cpu->interrupt_controller &= !IO_DEV_KBD_INTERRUPT;
        }

        // disk IO interrupt
        if (s->cpu->interrupt_controller & IO_DEV_DSK_INTERRUPT)
        {
            error |= sched_dskInterruptHandler(s);
            s->cpu->interrupt_controller &= !IO_DEV_DSK_INTERRUPT;
        }

        // video IO interrupt
        if (s->cpu->interrupt_controller & IO_DEV_VID_INTERRUPT)
        {
            error |= sched_vidInterruptHandler(s);
            s->cpu->interrupt_controller &= !IO_DEV_VID_INTERRUPT;
        }
    }

    return error;
}


/*=================================================================================================
 =  Comment block for function: sched_hardwareExceptionHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_hardwareExceptionHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handler for a hardware exception (DIV by 0).  Sends message to the console and grabs next ready
 =  process
 =
 ==================================================================================================*/
int sched_hardwareExceptionHandler(Scheduler s)
{
    int error = 0;
    if (s==NULL) return NULL_POINTER_ERROR;

    Process p = s->cpu->running_process;
    printf("(%s[%d] terminated (DIV by 0))", p->pcb->name, p_getPID(p, &error));

    error |= sched_getNextReady(s);

    return 0;
}

/*=================================================================================================
 =  Comment block for function: sched_kbdRequestHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_kbdRequestHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handles a system request for keyboard IO.  Sends the current process to the keyboard's blocked
 =  queue and gets the next process from the ready queue.
 =
 ==================================================================================================*/
int sched_kbdRequestHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    Process p = s->cpu->running_process;
    int step = s->cpu->program_counter;

    printf("(%s[%d]-step %d requests Keyboard IO)", p->pcb->name, p_getPID(p, &error), step);

    error |= iodev_setRequest(s->cpu->kbd);
    error |= sched_setBlocked(s,s->cpu->kbd->blocked_q);
    //error |= sched_getNextReady(s);

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_dskRequestHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_dskRequestHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handles a system request for Disk IO.
 =
 ==================================================================================================*/
int sched_dskRequestHandler(Scheduler s)
{
    int error = 0;

    Process p = s->cpu->running_process;
    int step = s->cpu->program_counter;

    printf("(%s[%d]-step %d requests Disk IO)", p->pcb->name, p_getPID(p, &error), step);

    error |= iodev_setRequest(s->cpu->dsk);
    error |= sched_setBlocked(s,s->cpu->dsk->blocked_q);
    //error |= sched_getNextReady(s);

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_vidRequestHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_vidRequestHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handles a system request for video device IO.
 =
 ==================================================================================================*/
int sched_vidRequestHandler(Scheduler s)
{
    int error = 0;

    Process p = s->cpu->running_process;
    int step = s->cpu->program_counter;

    printf("(%s[%d]-step %d requests Video IO)", p->pcb->name, p_getPID(p, &error), step);

    error |= iodev_setRequest(s->cpu->vid);
    error |= sched_setBlocked(s,s->cpu->vid->blocked_q);
    //error |= sched_getNextReady(s);

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_semUpRequestHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_semUpRequestHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handles a system request by the running process to raise a semaphore, releasing ownership of it.
 =  The semaphore will give ownership to the first process in its blocked queue, and the scheduler
 =  will dequeue it from the block queue to the ready queue.
 =
 ==================================================================================================*/
int sched_semUpRequestHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    Process p = s->cpu->running_process;
    int step = s->cpu->program_counter;

    printf("(%s[%d]-step %d releases semaphore)", p->pcb->name, p_getPID(p, &error), step);

    //if (!p_queue_isEmpty(s->cpu->sem->blocked_q, &error))
    //{
        //char* blocked = p_queue_peek(s->cpu->sem->blocked_q, &error)->pcb->name;
        //printf("%s claims semaphore", blocked);
    //}

    error |= semaphore_up(s->cpu->sem, s->cpu->running_process);
    error |= sched_getBlocked(s, s->cpu->sem->blocked_q);

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_semDwnRequestHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_semDwnRequestHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler
 =      Error conditions: NULL_POINTER_ERROR
 =  Handles a system request by the running process to take ownership of a semaphore.  If it fails
 =  to get it, the scheduler marks it as blocked and puts it in the semaphore's blocking queue.
 =
 ==================================================================================================*/
int sched_semDwnRequestHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    Process p = s->cpu->running_process;
    int step = s->cpu->program_counter;

    printf("(%s[%d]-step %d requests semaphore)", p->pcb->name, p_getPID(p, &error), step);

    error |= semaphore_down(s->cpu->sem, s->cpu->running_process);

    if (p_getOwns(s->cpu->running_process, &error) == NULL)     // failed to get ownership.
    {
        error |= sched_setBlocked(s, s->cpu->sem->blocked_q);   // Now blocked.
        //error |= sched_getNextReady(s);                         // switch context.
    }

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_timerInterruptHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_timerInterruptHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handler for a system timer interrupt.  Hmmm.  if we change context before
 =
 ==================================================================================================*/
int sched_timerInterruptHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    Process p = s->cpu->running_process;
    int step = s->cpu->program_counter;

    printf("(Timer interrupts %s[%d]-step %d)", p->pcb->name, p_getPID(p, &error), step);

    error |= sched_setReady(s, s->cpu->running_process);
    error |= sched_getNextReady(s);

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_kbdInterruptHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_kbdInterruptHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handler for a keyboard IO interrupt.  dequeues blocked process to the ready queue.  For the sim,
 =  we'll generate a new random interval if the block queue is not empty.
 =
 ==================================================================================================*/
int sched_kbdInterruptHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    //char* name = p_queue_peek(s->cpu->kbd->blocked_q, &error)->pcb->name;
    printf("(Keyboard Interrupt)");

    error |= sched_getBlocked(s, s->cpu->kbd->blocked_q);

    if (!p_queue_isEmpty(s->cpu->kbd->blocked_q, &error))   // if queue not empty,
    {
        error |= iodev_setRequest(s->cpu->kbd);             // reset the delay
    }

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_dskInterruptHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_dskInterruptHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handler for a disk IO interrupt.
 =
 ==================================================================================================*/
int sched_dskInterruptHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    //char* name = p_queue_peek(s->cpu->dsk->blocked_q, &error)->pcb->name;
    printf("(Disk Interrupt)");

    error |= sched_getBlocked(s, s->cpu->dsk->blocked_q);

    if (!p_queue_isEmpty(s->cpu->dsk->blocked_q, &error))   // if queue not empty,
    {
        error |= iodev_setRequest(s->cpu->dsk);             // reset the delay
    }

    return error;
}

/*=================================================================================================
 =  Comment block for function: sched_vidInterruptHandler()
 =  Programmer names: Evan Phillips, Lucas Cowell, Tony Butterfield
 =  Final test and approval date:
 =  Synopsis:
 =      int sched_vidInterruptHandler(Scheduler)
 =      Return value ranges: 0 for success, error code otherwise.
 =      Parameter value ranges: pointer to scheduler, pointer to process queue
 =      Error conditions: NULL_POINTER_ERROR
 =  Handler for a video IO interrupt.
 =
 ==================================================================================================*/
int sched_vidInterruptHandler(Scheduler s)
{
    int error = 0;
    if (s == NULL) return NULL_POINTER_ERROR;

    //char* name = p_queue_peek(s->cpu->vid->blocked_q, &error)->pcb->name;
    printf("(Video Interrupt)");

    error |= sched_getBlocked(s, s->cpu->vid->blocked_q);

    if (!p_queue_isEmpty(s->cpu->vid->blocked_q, &error))   // if queue not empty,
    {
        error |= iodev_setRequest(s->cpu->vid);             // reset the delay
    }

    return error;
}
